-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #181 from craigerl/unit-tests
Added unit test for client base
- Loading branch information
Showing
8 changed files
with
317 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import datetime | ||
import unittest | ||
from unittest import mock | ||
|
||
from aprsd import exception | ||
from aprsd.client.aprsis import APRSISClient | ||
|
||
|
||
class TestAPRSISClient(unittest.TestCase): | ||
"""Test cases for APRSISClient.""" | ||
|
||
def setUp(self): | ||
"""Set up test fixtures.""" | ||
super().setUp() | ||
|
||
# Mock the config | ||
self.mock_conf = mock.MagicMock() | ||
self.mock_conf.aprs_network.enabled = True | ||
self.mock_conf.aprs_network.login = "TEST" | ||
self.mock_conf.aprs_network.password = "12345" | ||
self.mock_conf.aprs_network.host = "localhost" | ||
self.mock_conf.aprs_network.port = 14580 | ||
|
||
@mock.patch("aprsd.client.base.APRSClient") | ||
@mock.patch("aprsd.client.drivers.aprsis.Aprsdis") | ||
def test_stats_not_configured(self, mock_aprsdis, mock_base): | ||
"""Test stats when client is not configured.""" | ||
mock_client = mock.MagicMock() | ||
mock_aprsdis.return_value = mock_client | ||
|
||
with mock.patch("aprsd.client.aprsis.cfg.CONF", self.mock_conf): | ||
self.client = APRSISClient() | ||
|
||
with mock.patch.object(APRSISClient, "is_configured", return_value=False): | ||
stats = self.client.stats() | ||
self.assertEqual({}, stats) | ||
|
||
@mock.patch("aprsd.client.base.APRSClient") | ||
@mock.patch("aprsd.client.drivers.aprsis.Aprsdis") | ||
def test_stats_configured(self, mock_aprsdis, mock_base): | ||
"""Test stats when client is configured.""" | ||
mock_client = mock.MagicMock() | ||
mock_aprsdis.return_value = mock_client | ||
|
||
with mock.patch("aprsd.client.aprsis.cfg.CONF", self.mock_conf): | ||
self.client = APRSISClient() | ||
|
||
mock_client = mock.MagicMock() | ||
mock_client.server_string = "test.server:14580" | ||
mock_client.aprsd_keepalive = datetime.datetime.now() | ||
self.client._client = mock_client | ||
self.client.filter = "m/50" | ||
|
||
with mock.patch.object(APRSISClient, "is_configured", return_value=True): | ||
stats = self.client.stats() | ||
self.assertEqual( | ||
{ | ||
"server_string": mock_client.server_string, | ||
"sever_keepalive": mock_client.aprsd_keepalive, | ||
"filter": "m/50", | ||
}, stats, | ||
) | ||
|
||
def test_is_configured_missing_login(self): | ||
"""Test is_configured with missing login.""" | ||
self.mock_conf.aprs_network.login = None | ||
with self.assertRaises(exception.MissingConfigOptionException): | ||
APRSISClient.is_configured() | ||
|
||
def test_is_configured_missing_password(self): | ||
"""Test is_configured with missing password.""" | ||
self.mock_conf.aprs_network.password = None | ||
with self.assertRaises(exception.MissingConfigOptionException): | ||
APRSISClient.is_configured() | ||
|
||
def test_is_configured_missing_host(self): | ||
"""Test is_configured with missing host.""" | ||
self.mock_conf.aprs_network.host = None | ||
with mock.patch("aprsd.client.aprsis.cfg.CONF", self.mock_conf): | ||
with self.assertRaises(exception.MissingConfigOptionException): | ||
APRSISClient.is_configured() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import unittest | ||
from unittest import mock | ||
|
||
from aprsd.client.factory import Client, ClientFactory | ||
|
||
|
||
class MockClient: | ||
"""Mock client for testing.""" | ||
|
||
@classmethod | ||
def is_enabled(cls): | ||
return True | ||
|
||
@classmethod | ||
def is_configured(cls): | ||
return True | ||
|
||
|
||
class TestClientFactory(unittest.TestCase): | ||
"""Test cases for ClientFactory.""" | ||
|
||
def setUp(self): | ||
"""Set up test fixtures.""" | ||
self.factory = ClientFactory() | ||
# Clear any registered clients from previous tests | ||
self.factory.clients = [] | ||
|
||
def test_singleton(self): | ||
"""Test that ClientFactory is a singleton.""" | ||
factory2 = ClientFactory() | ||
self.assertEqual(self.factory, factory2) | ||
|
||
def test_register_client(self): | ||
"""Test registering a client.""" | ||
self.factory.register(MockClient) | ||
self.assertIn(MockClient, self.factory.clients) | ||
|
||
def test_register_invalid_client(self): | ||
"""Test registering an invalid client raises error.""" | ||
invalid_client = mock.MagicMock(spec=Client) | ||
with self.assertRaises(ValueError): | ||
self.factory.register(invalid_client) | ||
|
||
def test_create_client(self): | ||
"""Test creating a client.""" | ||
self.factory.register(MockClient) | ||
client = self.factory.create() | ||
self.assertIsInstance(client, MockClient) | ||
|
||
def test_create_no_clients(self): | ||
"""Test creating a client with no registered clients.""" | ||
with self.assertRaises(Exception): | ||
self.factory.create() | ||
|
||
def test_is_client_enabled(self): | ||
"""Test checking if any client is enabled.""" | ||
self.factory.register(MockClient) | ||
self.assertTrue(self.factory.is_client_enabled()) | ||
|
||
def test_is_client_enabled_none(self): | ||
"""Test checking if any client is enabled when none are.""" | ||
MockClient.is_enabled = classmethod(lambda cls: False) | ||
self.factory.register(MockClient) | ||
self.assertFalse(self.factory.is_client_enabled()) | ||
|
||
def test_is_client_configured(self): | ||
"""Test checking if any client is configured.""" | ||
self.factory.register(MockClient) | ||
self.assertTrue(self.factory.is_client_configured()) | ||
|
||
def test_is_client_configured_none(self): | ||
"""Test checking if any client is configured when none are.""" | ||
MockClient.is_configured = classmethod(lambda cls: False) | ||
self.factory.register(MockClient) | ||
self.assertFalse(self.factory.is_client_configured()) |
Oops, something went wrong.