mirror of
https://github.com/craigerl/aprsd.git
synced 2026-06-07 08:34:40 -04:00
Reworked the entire client and drivers
This patch includes a completely reworked client structure. There is now only 1 client object, that loads the appropriate drivers. The drivers are fake, aprsis and tcpkiss. The TCPKISS client was written from scratch to avoid using asyncio. Asyncion is nothing but a pain in the ass.
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
import datetime
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprslib.exceptions import LoginError
|
||||
|
||||
from aprsd import exception
|
||||
from aprsd.client.drivers.aprsis import APRSISDriver
|
||||
from aprsd.client.drivers.registry import ClientDriver
|
||||
from aprsd.packets import core
|
||||
|
||||
|
||||
class TestAPRSISDriver(unittest.TestCase):
|
||||
"""Unit tests for the APRSISDriver class."""
|
||||
|
||||
def setUp(self):
|
||||
# Mock configuration
|
||||
self.conf_patcher = mock.patch('aprsd.client.drivers.aprsis.CONF')
|
||||
self.mock_conf = self.conf_patcher.start()
|
||||
|
||||
# Configure APRS-IS settings
|
||||
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 = 'rotate.aprs.net'
|
||||
self.mock_conf.aprs_network.port = 14580
|
||||
|
||||
# Mock APRS Lib Client
|
||||
self.aprslib_patcher = mock.patch('aprsd.client.drivers.aprsis.APRSLibClient')
|
||||
self.mock_aprslib = self.aprslib_patcher.start()
|
||||
self.mock_client = mock.MagicMock()
|
||||
self.mock_aprslib.return_value = self.mock_client
|
||||
|
||||
# Create an instance of the driver
|
||||
self.driver = APRSISDriver()
|
||||
|
||||
def tearDown(self):
|
||||
self.conf_patcher.stop()
|
||||
self.aprslib_patcher.stop()
|
||||
|
||||
def test_implements_client_driver_protocol(self):
|
||||
"""Test that APRSISDriver implements the ClientDriver Protocol."""
|
||||
# Verify the instance is recognized as implementing the Protocol
|
||||
self.assertIsInstance(self.driver, ClientDriver)
|
||||
|
||||
# Verify all required methods are present with correct signatures
|
||||
required_methods = [
|
||||
'is_enabled',
|
||||
'is_configured',
|
||||
'is_alive',
|
||||
'close',
|
||||
'send',
|
||||
'setup_connection',
|
||||
'set_filter',
|
||||
'login_success',
|
||||
'login_failure',
|
||||
'consumer',
|
||||
'decode_packet',
|
||||
'stats',
|
||||
]
|
||||
|
||||
for method_name in required_methods:
|
||||
self.assertTrue(
|
||||
hasattr(self.driver, method_name),
|
||||
f'Missing required method: {method_name}',
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization sets default values."""
|
||||
self.assertIsInstance(self.driver.max_delta, datetime.timedelta)
|
||||
self.assertEqual(self.driver.max_delta, datetime.timedelta(minutes=2))
|
||||
self.assertFalse(self.driver.login_status['success'])
|
||||
self.assertIsNone(self.driver.login_status['message'])
|
||||
self.assertIsNone(self.driver._client)
|
||||
|
||||
def test_is_enabled_true(self):
|
||||
"""Test is_enabled returns True when APRS-IS is enabled."""
|
||||
self.mock_conf.aprs_network.enabled = True
|
||||
self.assertTrue(APRSISDriver.is_enabled())
|
||||
|
||||
def test_is_enabled_false(self):
|
||||
"""Test is_enabled returns False when APRS-IS is disabled."""
|
||||
self.mock_conf.aprs_network.enabled = False
|
||||
self.assertFalse(APRSISDriver.is_enabled())
|
||||
|
||||
def test_is_enabled_key_error(self):
|
||||
"""Test is_enabled returns False when enabled flag doesn't exist."""
|
||||
self.mock_conf.aprs_network = mock.MagicMock()
|
||||
type(self.mock_conf.aprs_network).enabled = mock.PropertyMock(
|
||||
side_effect=KeyError
|
||||
)
|
||||
self.assertFalse(APRSISDriver.is_enabled())
|
||||
|
||||
def test_is_configured_true(self):
|
||||
"""Test is_configured returns True when properly configured."""
|
||||
with mock.patch.object(APRSISDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.aprs_network.login = 'TEST'
|
||||
self.mock_conf.aprs_network.password = '12345'
|
||||
self.mock_conf.aprs_network.host = 'rotate.aprs.net'
|
||||
|
||||
self.assertTrue(APRSISDriver.is_configured())
|
||||
|
||||
def test_is_configured_no_login(self):
|
||||
"""Test is_configured raises exception when login not set."""
|
||||
with mock.patch.object(APRSISDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.aprs_network.login = None
|
||||
|
||||
with self.assertRaises(exception.MissingConfigOptionException):
|
||||
APRSISDriver.is_configured()
|
||||
|
||||
def test_is_configured_no_password(self):
|
||||
"""Test is_configured raises exception when password not set."""
|
||||
with mock.patch.object(APRSISDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.aprs_network.login = 'TEST'
|
||||
self.mock_conf.aprs_network.password = None
|
||||
|
||||
with self.assertRaises(exception.MissingConfigOptionException):
|
||||
APRSISDriver.is_configured()
|
||||
|
||||
def test_is_configured_no_host(self):
|
||||
"""Test is_configured raises exception when host not set."""
|
||||
with mock.patch.object(APRSISDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.aprs_network.login = 'TEST'
|
||||
self.mock_conf.aprs_network.password = '12345'
|
||||
self.mock_conf.aprs_network.host = None
|
||||
|
||||
with self.assertRaises(exception.MissingConfigOptionException):
|
||||
APRSISDriver.is_configured()
|
||||
|
||||
def test_is_configured_disabled(self):
|
||||
"""Test is_configured returns True when not enabled."""
|
||||
with mock.patch.object(APRSISDriver, 'is_enabled', return_value=False):
|
||||
self.assertTrue(APRSISDriver.is_configured())
|
||||
|
||||
def test_is_alive_no_client(self):
|
||||
"""Test is_alive returns False when no client."""
|
||||
self.driver._client = None
|
||||
self.assertFalse(self.driver.is_alive)
|
||||
|
||||
def test_is_alive_true(self):
|
||||
"""Test is_alive returns True when client is alive and connection is not stale."""
|
||||
self.driver._client = self.mock_client
|
||||
self.mock_client.is_alive.return_value = True
|
||||
|
||||
with mock.patch.object(self.driver, '_is_stale_connection', return_value=False):
|
||||
self.assertTrue(self.driver.is_alive)
|
||||
|
||||
def test_is_alive_client_not_alive(self):
|
||||
"""Test is_alive returns False when client is not alive."""
|
||||
self.driver._client = self.mock_client
|
||||
self.mock_client.is_alive.return_value = False
|
||||
|
||||
with mock.patch.object(self.driver, '_is_stale_connection', return_value=False):
|
||||
self.assertFalse(self.driver.is_alive)
|
||||
|
||||
def test_is_alive_stale_connection(self):
|
||||
"""Test is_alive returns False when connection is stale."""
|
||||
self.driver._client = self.mock_client
|
||||
self.mock_client.is_alive.return_value = True
|
||||
|
||||
with mock.patch.object(self.driver, '_is_stale_connection', return_value=True):
|
||||
self.assertFalse(self.driver.is_alive)
|
||||
|
||||
def test_close(self):
|
||||
"""Test close method stops and closes the client."""
|
||||
self.driver._client = self.mock_client
|
||||
|
||||
self.driver.close()
|
||||
|
||||
self.mock_client.stop.assert_called_once()
|
||||
self.mock_client.close.assert_called_once()
|
||||
|
||||
def test_close_no_client(self):
|
||||
"""Test close method handles no client gracefully."""
|
||||
self.driver._client = None
|
||||
|
||||
# Should not raise exception
|
||||
self.driver.close()
|
||||
|
||||
def test_send(self):
|
||||
"""Test send passes packet to client."""
|
||||
self.driver._client = self.mock_client
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
|
||||
self.driver.send(mock_packet)
|
||||
|
||||
self.mock_client.send.assert_called_once_with(mock_packet)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
def test_setup_connection_success(self, mock_log):
|
||||
"""Test setup_connection successfully connects."""
|
||||
# Configure successful connection
|
||||
self.mock_client.server_string = 'Test APRS-IS Server'
|
||||
|
||||
self.driver.setup_connection()
|
||||
|
||||
# Check client created with correct parameters
|
||||
self.mock_aprslib.assert_called_once_with(
|
||||
self.mock_conf.aprs_network.login,
|
||||
passwd=self.mock_conf.aprs_network.password,
|
||||
host=self.mock_conf.aprs_network.host,
|
||||
port=self.mock_conf.aprs_network.port,
|
||||
)
|
||||
|
||||
# Check logger set and connection initialized
|
||||
self.assertEqual(self.mock_client.logger, mock_log)
|
||||
self.mock_client.connect.assert_called_once()
|
||||
|
||||
# Check status updated
|
||||
self.assertTrue(self.driver.connected)
|
||||
self.assertTrue(self.driver.login_status['success'])
|
||||
self.assertEqual(self.driver.login_status['message'], 'Test APRS-IS Server')
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
@mock.patch('aprsd.client.drivers.aprsis.time.sleep')
|
||||
def test_setup_connection_login_error(self, mock_sleep, mock_log):
|
||||
"""Test setup_connection handles login error."""
|
||||
# Configure login error
|
||||
login_error = LoginError('Bad login')
|
||||
login_error.message = 'Invalid login credentials'
|
||||
self.mock_client.connect.side_effect = login_error
|
||||
|
||||
self.driver.setup_connection()
|
||||
|
||||
# Check error logged
|
||||
mock_log.error.assert_any_call("Failed to login to APRS-IS Server 'Bad login'")
|
||||
mock_log.error.assert_any_call('Invalid login credentials')
|
||||
|
||||
# Check status updated
|
||||
self.assertFalse(self.driver.connected)
|
||||
self.assertFalse(self.driver.login_status['success'])
|
||||
self.assertEqual(
|
||||
self.driver.login_status['message'], 'Invalid login credentials'
|
||||
)
|
||||
|
||||
# Check backoff used
|
||||
mock_sleep.assert_called()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
@mock.patch('aprsd.client.drivers.aprsis.time.sleep')
|
||||
def test_setup_connection_general_error(self, mock_sleep, mock_log):
|
||||
"""Test setup_connection handles general error."""
|
||||
# Configure general exception
|
||||
error_message = 'Connection error'
|
||||
error = Exception(error_message)
|
||||
# Standard exceptions don't have a message attribute
|
||||
self.mock_client.connect.side_effect = error
|
||||
|
||||
self.driver.setup_connection()
|
||||
|
||||
# Check error logged
|
||||
mock_log.error.assert_any_call(
|
||||
f"Unable to connect to APRS-IS server. '{error_message}' "
|
||||
)
|
||||
|
||||
# Check status updated
|
||||
self.assertFalse(self.driver.connected)
|
||||
self.assertFalse(self.driver.login_status['success'])
|
||||
|
||||
# Check login message contains the error message (more flexible than exact equality)
|
||||
self.assertIn(error_message, self.driver.login_status['message'])
|
||||
|
||||
# Check backoff used
|
||||
mock_sleep.assert_called()
|
||||
|
||||
def test_set_filter(self):
|
||||
"""Test set_filter passes filter to client."""
|
||||
self.driver._client = self.mock_client
|
||||
test_filter = 'm/50'
|
||||
|
||||
self.driver.set_filter(test_filter)
|
||||
|
||||
self.mock_client.set_filter.assert_called_once_with(test_filter)
|
||||
|
||||
def test_login_success(self):
|
||||
"""Test login_success returns login status."""
|
||||
self.driver.login_status['success'] = True
|
||||
self.assertTrue(self.driver.login_success())
|
||||
|
||||
self.driver.login_status['success'] = False
|
||||
self.assertFalse(self.driver.login_success())
|
||||
|
||||
def test_login_failure(self):
|
||||
"""Test login_failure returns error message."""
|
||||
self.driver.login_status['message'] = None
|
||||
self.assertIsNone(self.driver.login_failure())
|
||||
|
||||
self.driver.login_status['message'] = 'Test error'
|
||||
self.assertEqual(self.driver.login_failure(), 'Test error')
|
||||
|
||||
def test_filter_property(self):
|
||||
"""Test filter property returns client filter."""
|
||||
self.driver._client = self.mock_client
|
||||
test_filter = 'm/50'
|
||||
self.mock_client.filter = test_filter
|
||||
|
||||
self.assertEqual(self.driver.filter, test_filter)
|
||||
|
||||
def test_server_string_property(self):
|
||||
"""Test server_string property returns client server string."""
|
||||
self.driver._client = self.mock_client
|
||||
test_string = 'Test APRS-IS Server'
|
||||
self.mock_client.server_string = test_string
|
||||
|
||||
self.assertEqual(self.driver.server_string, test_string)
|
||||
|
||||
def test_keepalive_property(self):
|
||||
"""Test keepalive property returns client keepalive."""
|
||||
self.driver._client = self.mock_client
|
||||
test_time = datetime.datetime.now()
|
||||
self.mock_client.aprsd_keepalive = test_time
|
||||
|
||||
self.assertEqual(self.driver.keepalive, test_time)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
def test_is_stale_connection_true(self, mock_log):
|
||||
"""Test _is_stale_connection returns True when connection is stale."""
|
||||
self.driver._client = self.mock_client
|
||||
# Set keepalive to 3 minutes ago (exceeds max_delta of 2 minutes)
|
||||
self.mock_client.aprsd_keepalive = datetime.datetime.now() - datetime.timedelta(
|
||||
minutes=3
|
||||
)
|
||||
|
||||
result = self.driver._is_stale_connection()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_log.error.assert_called_once()
|
||||
|
||||
def test_is_stale_connection_false(self):
|
||||
"""Test _is_stale_connection returns False when connection is not stale."""
|
||||
self.driver._client = self.mock_client
|
||||
# Set keepalive to 1 minute ago (within max_delta of 2 minutes)
|
||||
self.mock_client.aprsd_keepalive = datetime.datetime.now() - datetime.timedelta(
|
||||
minutes=1
|
||||
)
|
||||
|
||||
result = self.driver._is_stale_connection()
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_transport(self):
|
||||
"""Test transport returns appropriate transport type."""
|
||||
self.assertEqual(APRSISDriver.transport(), 'aprsis')
|
||||
|
||||
def test_decode_packet(self):
|
||||
"""Test decode_packet uses core.factory."""
|
||||
with mock.patch('aprsd.client.drivers.aprsis.core.factory') as mock_factory:
|
||||
raw_packet = {'from': 'TEST', 'to': 'APRS'}
|
||||
self.driver.decode_packet(raw_packet)
|
||||
mock_factory.assert_called_once_with(raw_packet)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
def test_consumer_success(self, mock_log):
|
||||
"""Test consumer forwards callback to client."""
|
||||
self.driver._client = self.mock_client
|
||||
mock_callback = mock.MagicMock()
|
||||
|
||||
self.driver.consumer(mock_callback, raw=True)
|
||||
|
||||
self.mock_client.consumer.assert_called_once_with(
|
||||
mock_callback, blocking=False, immortal=False, raw=True
|
||||
)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
def test_consumer_exception(self, mock_log):
|
||||
"""Test consumer handles exceptions."""
|
||||
self.driver._client = self.mock_client
|
||||
mock_callback = mock.MagicMock()
|
||||
test_error = Exception('Test error')
|
||||
self.mock_client.consumer.side_effect = test_error
|
||||
|
||||
with self.assertRaises(Exception): # noqa: B017
|
||||
self.driver.consumer(mock_callback)
|
||||
|
||||
mock_log.error.assert_called_with(test_error)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.aprsis.LOG')
|
||||
def test_consumer_no_client(self, mock_log):
|
||||
"""Test consumer handles no client gracefully."""
|
||||
self.driver._client = None
|
||||
mock_callback = mock.MagicMock()
|
||||
|
||||
self.driver.consumer(mock_callback)
|
||||
|
||||
mock_log.warning.assert_called_once()
|
||||
self.assertFalse(self.driver.connected)
|
||||
|
||||
def test_stats_configured_with_client(self):
|
||||
"""Test stats returns correct data when configured with client."""
|
||||
# Configure driver
|
||||
with mock.patch.object(self.driver, 'is_configured', return_value=True):
|
||||
self.driver._client = self.mock_client
|
||||
self.mock_client.aprsd_keepalive = datetime.datetime.now()
|
||||
self.mock_client.server_string = 'Test Server'
|
||||
self.mock_client.filter = 'm/50'
|
||||
|
||||
stats = self.driver.stats()
|
||||
|
||||
self.assertEqual(stats['connected'], True)
|
||||
self.assertEqual(stats['filter'], 'm/50')
|
||||
self.assertEqual(stats['server_string'], 'Test Server')
|
||||
self.assertEqual(stats['transport'], 'aprsis')
|
||||
|
||||
def test_stats_serializable(self):
|
||||
"""Test stats with serializable=True converts datetime to ISO format."""
|
||||
# Configure driver
|
||||
with mock.patch.object(self.driver, 'is_configured', return_value=True):
|
||||
self.driver._client = self.mock_client
|
||||
test_time = datetime.datetime.now()
|
||||
self.mock_client.aprsd_keepalive = test_time
|
||||
|
||||
stats = self.driver.stats(serializable=True)
|
||||
|
||||
# Check keepalive is a string in ISO format
|
||||
self.assertIsInstance(stats['connection_keepalive'], str)
|
||||
# Try parsing it to verify it's a valid ISO format
|
||||
try:
|
||||
datetime.datetime.fromisoformat(stats['connection_keepalive'])
|
||||
except ValueError:
|
||||
self.fail('keepalive is not in valid ISO format')
|
||||
|
||||
def test_stats_no_client(self):
|
||||
"""Test stats with no client."""
|
||||
with mock.patch.object(self.driver, 'is_configured', return_value=True):
|
||||
self.driver._client = None
|
||||
|
||||
stats = self.driver.stats()
|
||||
|
||||
self.assertEqual(stats['connection_keepalive'], 'None')
|
||||
self.assertEqual(stats['server_string'], 'None')
|
||||
|
||||
def test_stats_not_configured(self):
|
||||
"""Test stats when not configured returns empty dict."""
|
||||
with mock.patch.object(self.driver, 'is_configured', return_value=False):
|
||||
stats = self.driver.stats()
|
||||
self.assertEqual(stats, {})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,191 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.client.drivers.fake import APRSDFakeDriver
|
||||
from aprsd.packets import core
|
||||
|
||||
|
||||
class TestAPRSDFakeDriver(unittest.TestCase):
|
||||
"""Unit tests for the APRSDFakeDriver class."""
|
||||
|
||||
def setUp(self):
|
||||
# Mock CONF for testing
|
||||
self.conf_patcher = mock.patch('aprsd.client.drivers.fake.CONF')
|
||||
self.mock_conf = self.conf_patcher.start()
|
||||
|
||||
# Configure fake_client.enabled
|
||||
self.mock_conf.fake_client.enabled = True
|
||||
|
||||
# Create an instance of the driver
|
||||
self.driver = APRSDFakeDriver()
|
||||
|
||||
def tearDown(self):
|
||||
self.conf_patcher.stop()
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization sets default values."""
|
||||
self.assertEqual(self.driver.path, ['WIDE1-1', 'WIDE2-1'])
|
||||
self.assertFalse(self.driver.thread_stop)
|
||||
|
||||
def test_is_enabled_true(self):
|
||||
"""Test is_enabled returns True when configured."""
|
||||
self.mock_conf.fake_client.enabled = True
|
||||
self.assertTrue(APRSDFakeDriver.is_enabled())
|
||||
|
||||
def test_is_enabled_false(self):
|
||||
"""Test is_enabled returns False when not configured."""
|
||||
self.mock_conf.fake_client.enabled = False
|
||||
self.assertFalse(APRSDFakeDriver.is_enabled())
|
||||
|
||||
def test_is_alive(self):
|
||||
"""Test is_alive returns True when thread_stop is False."""
|
||||
self.driver.thread_stop = False
|
||||
self.assertTrue(self.driver.is_alive())
|
||||
|
||||
self.driver.thread_stop = True
|
||||
self.assertFalse(self.driver.is_alive())
|
||||
|
||||
def test_close(self):
|
||||
"""Test close sets thread_stop to True."""
|
||||
self.driver.thread_stop = False
|
||||
self.driver.close()
|
||||
self.assertTrue(self.driver.thread_stop)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.LOG')
|
||||
def test_setup_connection(self, mock_log):
|
||||
"""Test setup_connection does nothing (it's fake)."""
|
||||
self.driver.setup_connection()
|
||||
# Method doesn't do anything, so just verify it doesn't crash
|
||||
|
||||
def test_set_filter(self):
|
||||
"""Test set_filter method does nothing (it's fake)."""
|
||||
# Just test it doesn't fail
|
||||
self.driver.set_filter('test/filter')
|
||||
|
||||
def test_login_success(self):
|
||||
"""Test login_success always returns True."""
|
||||
self.assertTrue(self.driver.login_success())
|
||||
|
||||
def test_login_failure(self):
|
||||
"""Test login_failure always returns None."""
|
||||
self.assertIsNone(self.driver.login_failure())
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.LOG')
|
||||
def test_send_with_packet_object(self, mock_log):
|
||||
"""Test send with a Packet object."""
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
mock_packet.payload = 'Test payload'
|
||||
mock_packet.to_call = 'TEST'
|
||||
mock_packet.from_call = 'FAKE'
|
||||
|
||||
self.driver.send(mock_packet)
|
||||
|
||||
mock_log.info.assert_called_once()
|
||||
mock_packet.prepare.assert_called_once()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.LOG')
|
||||
def test_send_with_non_packet_object(self, mock_log):
|
||||
"""Test send with a non-Packet object."""
|
||||
# Create a mock message-like object
|
||||
mock_msg = mock.MagicMock()
|
||||
mock_msg.raw = 'Test'
|
||||
mock_msg.msgNo = '123'
|
||||
mock_msg.to_call = 'TEST'
|
||||
mock_msg.from_call = 'FAKE'
|
||||
|
||||
self.driver.send(mock_msg)
|
||||
|
||||
mock_log.info.assert_called_once()
|
||||
mock_log.debug.assert_called_once()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.LOG')
|
||||
@mock.patch('aprsd.client.drivers.fake.time.sleep')
|
||||
def test_consumer_with_raw_true(self, mock_sleep, mock_log):
|
||||
"""Test consumer with raw=True."""
|
||||
mock_callback = mock.MagicMock()
|
||||
|
||||
self.driver.consumer(mock_callback, raw=True)
|
||||
|
||||
# Verify callback was called with raw data
|
||||
mock_callback.assert_called_once()
|
||||
call_args = mock_callback.call_args[1]
|
||||
self.assertIn('raw', call_args)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.LOG')
|
||||
@mock.patch('aprsd.client.drivers.fake.aprslib.parse')
|
||||
@mock.patch('aprsd.client.drivers.fake.core.factory')
|
||||
@mock.patch('aprsd.client.drivers.fake.time.sleep')
|
||||
def test_consumer_with_raw_false(
|
||||
self, mock_sleep, mock_factory, mock_parse, mock_log
|
||||
):
|
||||
"""Test consumer with raw=False."""
|
||||
mock_callback = mock.MagicMock()
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
mock_factory.return_value = mock_packet
|
||||
|
||||
self.driver.consumer(mock_callback, raw=False)
|
||||
|
||||
# Verify the packet was created and passed to callback
|
||||
mock_parse.assert_called_once()
|
||||
mock_factory.assert_called_once()
|
||||
mock_callback.assert_called_once_with(packet=mock_packet)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
def test_consumer_updates_keepalive(self):
|
||||
"""Test consumer updates keepalive timestamp."""
|
||||
mock_callback = mock.MagicMock()
|
||||
old_keepalive = self.driver.aprsd_keepalive
|
||||
|
||||
# Force a small delay to ensure timestamp changes
|
||||
import time
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
with mock.patch('aprsd.client.drivers.fake.time.sleep'):
|
||||
self.driver.consumer(mock_callback)
|
||||
|
||||
self.assertNotEqual(old_keepalive, self.driver.aprsd_keepalive)
|
||||
self.assertGreater(self.driver.aprsd_keepalive, old_keepalive)
|
||||
|
||||
def test_decode_packet_with_empty_kwargs(self):
|
||||
"""Test decode_packet with empty kwargs."""
|
||||
result = self.driver.decode_packet()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_decode_packet_with_packet(self):
|
||||
"""Test decode_packet with packet in kwargs."""
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
result = self.driver.decode_packet(packet=mock_packet)
|
||||
self.assertEqual(result, mock_packet)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.fake.aprslib.parse')
|
||||
@mock.patch('aprsd.client.drivers.fake.core.factory')
|
||||
def test_decode_packet_with_raw(self, mock_factory, mock_parse):
|
||||
"""Test decode_packet with raw in kwargs."""
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
mock_factory.return_value = mock_packet
|
||||
raw_data = 'raw packet data'
|
||||
|
||||
result = self.driver.decode_packet(raw=raw_data)
|
||||
|
||||
mock_parse.assert_called_once_with(raw_data)
|
||||
mock_factory.assert_called_once_with(mock_parse.return_value)
|
||||
self.assertEqual(result, mock_packet)
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats returns correct information."""
|
||||
self.driver.thread_stop = False
|
||||
result = self.driver.stats()
|
||||
|
||||
self.assertEqual(result['driver'], 'APRSDFakeDriver')
|
||||
self.assertTrue(result['is_alive'])
|
||||
|
||||
# Test with serializable parameter
|
||||
result_serializable = self.driver.stats(serializable=True)
|
||||
self.assertEqual(result_serializable['driver'], 'APRSDFakeDriver')
|
||||
self.assertTrue(result_serializable['is_alive'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,498 @@
|
||||
import datetime
|
||||
import socket
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import aprslib
|
||||
|
||||
from aprsd import exception
|
||||
from aprsd.client.drivers.registry import ClientDriver
|
||||
from aprsd.client.drivers.tcpkiss import TCPKISSDriver
|
||||
from aprsd.packets import core
|
||||
|
||||
|
||||
class TestTCPKISSDriver(unittest.TestCase):
|
||||
"""Unit tests for the TCPKISSDriver class."""
|
||||
|
||||
def setUp(self):
|
||||
# Mock configuration
|
||||
self.conf_patcher = mock.patch('aprsd.client.drivers.tcpkiss.CONF')
|
||||
self.mock_conf = self.conf_patcher.start()
|
||||
|
||||
# Configure KISS settings
|
||||
self.mock_conf.kiss_tcp.enabled = True
|
||||
self.mock_conf.kiss_tcp.host = '127.0.0.1'
|
||||
self.mock_conf.kiss_tcp.port = 8001
|
||||
self.mock_conf.kiss_tcp.path = ['WIDE1-1', 'WIDE2-1']
|
||||
|
||||
# Mock socket
|
||||
self.socket_patcher = mock.patch('aprsd.client.drivers.tcpkiss.socket')
|
||||
self.mock_socket_module = self.socket_patcher.start()
|
||||
self.mock_socket = mock.MagicMock()
|
||||
self.mock_socket_module.socket.return_value = self.mock_socket
|
||||
|
||||
# Mock select
|
||||
self.select_patcher = mock.patch('aprsd.client.drivers.tcpkiss.select')
|
||||
self.mock_select = self.select_patcher.start()
|
||||
|
||||
# Create an instance of the driver
|
||||
self.driver = TCPKISSDriver()
|
||||
|
||||
def tearDown(self):
|
||||
self.conf_patcher.stop()
|
||||
self.socket_patcher.stop()
|
||||
self.select_patcher.stop()
|
||||
|
||||
def test_implements_client_driver_protocol(self):
|
||||
"""Test that TCPKISSDriver implements the ClientDriver Protocol."""
|
||||
# Verify the instance is recognized as implementing the Protocol
|
||||
self.assertIsInstance(self.driver, ClientDriver)
|
||||
|
||||
# Verify all required methods are present with correct signatures
|
||||
required_methods = [
|
||||
'is_enabled',
|
||||
'is_configured',
|
||||
'is_alive',
|
||||
'close',
|
||||
'send',
|
||||
'setup_connection',
|
||||
'set_filter',
|
||||
'login_success',
|
||||
'login_failure',
|
||||
'consumer',
|
||||
'decode_packet',
|
||||
'stats',
|
||||
]
|
||||
|
||||
for method_name in required_methods:
|
||||
self.assertTrue(
|
||||
hasattr(self.driver, method_name),
|
||||
f'Missing required method: {method_name}',
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization sets default values."""
|
||||
self.assertFalse(self.driver._connected)
|
||||
self.assertIsInstance(self.driver.keepalive, datetime.datetime)
|
||||
self.assertFalse(self.driver._running)
|
||||
|
||||
def test_transport_property(self):
|
||||
"""Test transport property returns correct value."""
|
||||
self.assertEqual(self.driver.transport, 'tcpkiss')
|
||||
|
||||
def test_is_enabled_true(self):
|
||||
"""Test is_enabled returns True when KISS TCP is enabled."""
|
||||
self.mock_conf.kiss_tcp.enabled = True
|
||||
self.assertTrue(TCPKISSDriver.is_enabled())
|
||||
|
||||
def test_is_enabled_false(self):
|
||||
"""Test is_enabled returns False when KISS TCP is disabled."""
|
||||
self.mock_conf.kiss_tcp.enabled = False
|
||||
self.assertFalse(TCPKISSDriver.is_enabled())
|
||||
|
||||
def test_is_configured_true(self):
|
||||
"""Test is_configured returns True when properly configured."""
|
||||
with mock.patch.object(TCPKISSDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.kiss_tcp.host = '127.0.0.1'
|
||||
self.assertTrue(TCPKISSDriver.is_configured())
|
||||
|
||||
def test_is_configured_false_no_host(self):
|
||||
"""Test is_configured returns False when host not set."""
|
||||
with mock.patch.object(TCPKISSDriver, 'is_enabled', return_value=True):
|
||||
self.mock_conf.kiss_tcp.host = None
|
||||
with self.assertRaises(exception.MissingConfigOptionException):
|
||||
TCPKISSDriver.is_configured()
|
||||
|
||||
def test_is_configured_false_not_enabled(self):
|
||||
"""Test is_configured returns False when not enabled."""
|
||||
with mock.patch.object(TCPKISSDriver, 'is_enabled', return_value=False):
|
||||
self.assertFalse(TCPKISSDriver.is_configured())
|
||||
|
||||
def test_is_alive(self):
|
||||
"""Test is_alive property returns connection state."""
|
||||
self.driver._connected = True
|
||||
self.assertTrue(self.driver.is_alive)
|
||||
|
||||
self.driver._connected = False
|
||||
self.assertFalse(self.driver.is_alive)
|
||||
|
||||
def test_close(self):
|
||||
"""Test close method calls stop."""
|
||||
with mock.patch.object(self.driver, 'stop') as mock_stop:
|
||||
self.driver.close()
|
||||
mock_stop.assert_called_once()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_setup_connection_success(self, mock_log):
|
||||
"""Test setup_connection successfully connects."""
|
||||
# Mock the connect method to succeed
|
||||
is_en = self.driver.is_enabled
|
||||
is_con = self.driver.is_configured
|
||||
self.driver.is_enabled = mock.MagicMock(return_value=True)
|
||||
self.driver.is_configured = mock.MagicMock(return_value=True)
|
||||
with mock.patch.object(
|
||||
self.driver, 'connect', return_value=True
|
||||
) as mock_connect:
|
||||
self.driver.setup_connection()
|
||||
mock_connect.assert_called_once()
|
||||
mock_log.info.assert_called_with('KISS TCP Connection to 127.0.0.1:8001')
|
||||
|
||||
self.driver.is_enabled = is_en
|
||||
self.driver.is_configured = is_con
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_setup_connection_failure(self, mock_log):
|
||||
"""Test setup_connection handles connection failure."""
|
||||
# Mock the connect method to fail
|
||||
with mock.patch.object(
|
||||
self.driver, 'connect', return_value=False
|
||||
) as mock_connect:
|
||||
self.driver.setup_connection()
|
||||
mock_connect.assert_called_once()
|
||||
mock_log.error.assert_called_with('Failed to connect to KISS interface')
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_setup_connection_exception(self, mock_log):
|
||||
"""Test setup_connection handles exceptions."""
|
||||
# Mock the connect method to raise an exception
|
||||
with mock.patch.object(
|
||||
self.driver, 'connect', side_effect=Exception('Test error')
|
||||
) as mock_connect:
|
||||
self.driver.setup_connection()
|
||||
mock_connect.assert_called_once()
|
||||
mock_log.error.assert_any_call('Failed to initialize KISS interface')
|
||||
mock_log.exception.assert_called_once()
|
||||
self.assertFalse(self.driver._connected)
|
||||
|
||||
def test_set_filter(self):
|
||||
"""Test set_filter does nothing for KISS."""
|
||||
# Just ensure it doesn't fail
|
||||
self.driver.set_filter('test/filter')
|
||||
|
||||
def test_login_success_when_connected(self):
|
||||
"""Test login_success returns True when connected."""
|
||||
self.driver._connected = True
|
||||
self.assertTrue(self.driver.login_success())
|
||||
|
||||
def test_login_success_when_not_connected(self):
|
||||
"""Test login_success returns False when not connected."""
|
||||
self.driver._connected = False
|
||||
self.assertFalse(self.driver.login_success())
|
||||
|
||||
def test_login_failure(self):
|
||||
"""Test login_failure returns success message."""
|
||||
self.assertEqual(self.driver.login_failure(), 'Login successful')
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.ax25frame.Frame.ui')
|
||||
def test_send_packet(self, mock_frame_ui):
|
||||
"""Test sending an APRS packet."""
|
||||
# Create a mock frame
|
||||
mock_frame = mock.MagicMock()
|
||||
mock_frame_bytes = b'mock_frame_data'
|
||||
mock_frame.__bytes__ = mock.MagicMock(return_value=mock_frame_bytes)
|
||||
mock_frame_ui.return_value = mock_frame
|
||||
|
||||
# Set up the driver
|
||||
self.driver.socket = self.mock_socket
|
||||
self.driver.path = ['WIDE1-1', 'WIDE2-1']
|
||||
|
||||
# Create a mock packet
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
mock_bytes = b'Test packet data'
|
||||
mock_packet.__bytes__ = mock.MagicMock(return_value=mock_bytes)
|
||||
# Add path attribute to the mock packet
|
||||
mock_packet.path = None
|
||||
|
||||
# Send the packet
|
||||
self.driver.send(mock_packet)
|
||||
|
||||
# Check that frame was created correctly
|
||||
mock_frame_ui.assert_called_once_with(
|
||||
destination='APZ100',
|
||||
source=mock_packet.from_call,
|
||||
path=self.driver.path,
|
||||
info=mock_packet.payload.encode('US-ASCII'),
|
||||
)
|
||||
|
||||
# Check that socket send was called
|
||||
self.mock_socket.send.assert_called_once()
|
||||
|
||||
# Verify packet counters updated
|
||||
self.assertEqual(self.driver.packets_sent, 1)
|
||||
self.assertIsNotNone(self.driver.last_packet_sent)
|
||||
|
||||
def test_send_with_no_socket(self):
|
||||
"""Test send raises exception when socket not initialized."""
|
||||
self.driver.socket = None
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
self.driver.send(mock_packet)
|
||||
self.assertIn('KISS interface not initialized', str(context.exception))
|
||||
|
||||
def test_stop(self):
|
||||
"""Test stop method cleans up properly."""
|
||||
self.driver._running = True
|
||||
self.driver._connected = True
|
||||
self.driver.socket = self.mock_socket
|
||||
|
||||
self.driver.stop()
|
||||
|
||||
self.assertFalse(self.driver._running)
|
||||
self.assertFalse(self.driver._connected)
|
||||
self.mock_socket.close.assert_called_once()
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats method returns correct data."""
|
||||
# Set up test data
|
||||
self.driver._connected = True
|
||||
self.driver.path = ['WIDE1-1', 'WIDE2-1']
|
||||
self.driver.packets_sent = 5
|
||||
self.driver.packets_received = 3
|
||||
self.driver.last_packet_sent = datetime.datetime.now()
|
||||
self.driver.last_packet_received = datetime.datetime.now()
|
||||
|
||||
# Get stats
|
||||
stats = self.driver.stats()
|
||||
|
||||
# Check stats contains expected keys
|
||||
expected_keys = [
|
||||
'client',
|
||||
'transport',
|
||||
'connected',
|
||||
'path',
|
||||
'packets_sent',
|
||||
'packets_received',
|
||||
'last_packet_sent',
|
||||
'last_packet_received',
|
||||
'connection_keepalive',
|
||||
'host',
|
||||
'port',
|
||||
]
|
||||
for key in expected_keys:
|
||||
self.assertIn(key, stats)
|
||||
|
||||
# Check some specific values
|
||||
self.assertEqual(stats['client'], 'TCPKISSDriver')
|
||||
self.assertEqual(stats['transport'], 'tcpkiss')
|
||||
self.assertEqual(stats['connected'], True)
|
||||
self.assertEqual(stats['packets_sent'], 5)
|
||||
self.assertEqual(stats['packets_received'], 3)
|
||||
|
||||
def test_stats_serializable(self):
|
||||
"""Test stats with serializable=True converts datetime to ISO format."""
|
||||
self.driver.keepalive = datetime.datetime.now()
|
||||
|
||||
stats = self.driver.stats(serializable=True)
|
||||
|
||||
# Check keepalive is a string in ISO format
|
||||
self.assertIsInstance(stats['connection_keepalive'], str)
|
||||
# Try parsing it to verify it's a valid ISO format
|
||||
try:
|
||||
datetime.datetime.fromisoformat(stats['connection_keepalive'])
|
||||
except ValueError:
|
||||
self.fail('keepalive is not in valid ISO format')
|
||||
|
||||
def test_connect_success(self):
|
||||
"""Test successful connection."""
|
||||
result = self.driver.connect()
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(self.driver._connected)
|
||||
self.mock_socket.connect.assert_called_once_with(
|
||||
(self.mock_conf.kiss_tcp.host, self.mock_conf.kiss_tcp.port)
|
||||
)
|
||||
self.mock_socket.settimeout.assert_any_call(5.0)
|
||||
self.mock_socket.settimeout.assert_any_call(0.1)
|
||||
|
||||
def test_connect_failure_socket_error(self):
|
||||
"""Test connection failure due to socket error."""
|
||||
self.mock_socket.connect.side_effect = socket.error('Test socket error')
|
||||
|
||||
result = self.driver.connect()
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertFalse(self.driver._connected)
|
||||
|
||||
def test_connect_failure_timeout(self):
|
||||
"""Test connection failure due to timeout."""
|
||||
self.mock_socket.connect.side_effect = socket.timeout('Test timeout')
|
||||
|
||||
result = self.driver.connect()
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertFalse(self.driver._connected)
|
||||
|
||||
def test_fix_raw_frame(self):
|
||||
"""Test fix_raw_frame removes KISS markers and handles FEND."""
|
||||
# Create a test frame with KISS markers
|
||||
with mock.patch(
|
||||
'aprsd.client.drivers.tcpkiss.handle_fend', return_value=b'fixed_frame'
|
||||
) as mock_handle_fend:
|
||||
raw_frame = b'\xc0\x00some_frame_data\xc0' # \xc0 is FEND
|
||||
|
||||
result = self.driver.fix_raw_frame(raw_frame)
|
||||
|
||||
mock_handle_fend.assert_called_once_with(b'some_frame_data')
|
||||
self.assertEqual(result, b'fixed_frame')
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_decode_packet_success(self, mock_log):
|
||||
"""Test successful packet decoding."""
|
||||
mock_frame = 'test frame data'
|
||||
mock_aprs_data = {'from': 'TEST-1', 'to': 'APRS'}
|
||||
mock_packet = mock.MagicMock(spec=core.Packet)
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.client.drivers.tcpkiss.aprslib.parse', return_value=mock_aprs_data
|
||||
) as mock_parse:
|
||||
with mock.patch(
|
||||
'aprsd.client.drivers.tcpkiss.core.factory', return_value=mock_packet
|
||||
) as mock_factory:
|
||||
result = self.driver.decode_packet(frame=mock_frame)
|
||||
|
||||
mock_parse.assert_called_once_with(str(mock_frame))
|
||||
mock_factory.assert_called_once_with(mock_aprs_data)
|
||||
self.assertEqual(result, mock_packet)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_decode_packet_no_frame(self, mock_log):
|
||||
"""Test decode_packet with no frame returns None."""
|
||||
result = self.driver.decode_packet()
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_log.warning.assert_called_once()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_decode_packet_exception(self, mock_log):
|
||||
"""Test decode_packet handles exceptions."""
|
||||
mock_frame = 'invalid frame'
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.client.drivers.tcpkiss.aprslib.parse',
|
||||
side_effect=Exception('Test error'),
|
||||
) as mock_parse:
|
||||
result = self.driver.decode_packet(frame=mock_frame)
|
||||
|
||||
mock_parse.assert_called_once()
|
||||
self.assertIsNone(result)
|
||||
mock_log.error.assert_called_once()
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_consumer_with_frame(self, mock_log):
|
||||
"""Test consumer processes frames and calls callback."""
|
||||
mock_callback = mock.MagicMock()
|
||||
mock_frame = mock.MagicMock()
|
||||
|
||||
# Configure driver for test
|
||||
self.driver._connected = True
|
||||
self.driver._running = True
|
||||
|
||||
# Set up read_frame to return one frame then stop
|
||||
def side_effect():
|
||||
self.driver._running = False
|
||||
return mock_frame
|
||||
|
||||
with mock.patch.object(
|
||||
self.driver, 'read_frame', side_effect=side_effect
|
||||
) as mock_read_frame:
|
||||
self.driver.consumer(mock_callback)
|
||||
|
||||
mock_read_frame.assert_called_once()
|
||||
mock_callback.assert_called_once_with(frame=mock_frame)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_consumer_with_connect_reconnect(self, mock_log):
|
||||
"""Test consumer tries to reconnect when not connected."""
|
||||
mock_callback = mock.MagicMock()
|
||||
|
||||
# Configure driver for test
|
||||
self.driver._connected = False
|
||||
|
||||
# Setup to run once then stop
|
||||
call_count = 0
|
||||
|
||||
def connect_side_effect():
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
# On second call, connect successfully
|
||||
if call_count == 2:
|
||||
self.driver._running = False
|
||||
self.driver.socket = self.mock_socket
|
||||
return True
|
||||
return False
|
||||
|
||||
with mock.patch.object(
|
||||
self.driver, 'connect', side_effect=connect_side_effect
|
||||
) as mock_connect:
|
||||
with mock.patch('aprsd.client.drivers.tcpkiss.time.sleep') as mock_sleep:
|
||||
self.driver.consumer(mock_callback)
|
||||
|
||||
self.assertEqual(mock_connect.call_count, 2)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_read_frame_success(self, mock_log):
|
||||
"""Test read_frame successfully reads a frame."""
|
||||
# Set up driver
|
||||
self.driver.socket = self.mock_socket
|
||||
self.driver._running = True
|
||||
|
||||
# Mock socket recv to return data
|
||||
raw_data = b'\xc0\x00test_frame\xc0'
|
||||
self.mock_socket.recv.return_value = raw_data
|
||||
|
||||
# Mock select to indicate socket is readable
|
||||
self.mock_select.select.return_value = ([self.mock_socket], [], [])
|
||||
|
||||
# Mock fix_raw_frame and Frame.from_bytes
|
||||
mock_fixed_frame = b'fixed_frame'
|
||||
mock_ax25_frame = mock.MagicMock()
|
||||
|
||||
with mock.patch.object(
|
||||
self.driver, 'fix_raw_frame', return_value=mock_fixed_frame
|
||||
) as mock_fix:
|
||||
with mock.patch(
|
||||
'aprsd.client.drivers.tcpkiss.ax25frame.Frame.from_bytes',
|
||||
return_value=mock_ax25_frame,
|
||||
) as mock_from_bytes:
|
||||
result = self.driver.read_frame()
|
||||
|
||||
self.mock_socket.setblocking.assert_called_once_with(0)
|
||||
self.mock_select.select.assert_called_once()
|
||||
self.mock_socket.recv.assert_called_once()
|
||||
mock_fix.assert_called_once_with(raw_data)
|
||||
mock_from_bytes.assert_called_once_with(mock_fixed_frame)
|
||||
self.assertEqual(result, mock_ax25_frame)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_read_frame_select_timeout(self, mock_log):
|
||||
"""Test read_frame handles select timeout."""
|
||||
# Set up driver
|
||||
self.driver.socket = self.mock_socket
|
||||
self.driver._running = True
|
||||
|
||||
# Mock select to indicate no readable sockets
|
||||
self.mock_select.select.return_value = ([], [], [])
|
||||
|
||||
result = self.driver.read_frame()
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('aprsd.client.drivers.tcpkiss.LOG')
|
||||
def test_read_frame_socket_error(self, mock_log):
|
||||
"""Test read_frame handles socket error."""
|
||||
# Set up driver
|
||||
self.driver.socket = self.mock_socket
|
||||
self.driver._running = True
|
||||
|
||||
# Mock setblocking to raise OSError
|
||||
self.mock_socket.setblocking.side_effect = OSError('Test error')
|
||||
|
||||
with self.assertRaises(aprslib.ConnectionDrop):
|
||||
self.driver.read_frame()
|
||||
mock_log.error.assert_called_once()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,89 +0,0 @@
|
||||
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()
|
||||
from rich.console import Console
|
||||
|
||||
c = Console()
|
||||
c.print(stats)
|
||||
self.assertEqual(
|
||||
{
|
||||
"connected": True,
|
||||
"filter": "m/50",
|
||||
"login_status": {"message": mock.ANY, "success": True},
|
||||
"connection_keepalive": mock_client.aprsd_keepalive,
|
||||
"server_string": mock_client.server_string,
|
||||
"transport": "aprsis",
|
||||
},
|
||||
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()
|
||||
@@ -1,141 +0,0 @@
|
||||
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()
|
||||
@@ -1,75 +0,0 @@
|
||||
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())
|
||||
@@ -0,0 +1,100 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.client.drivers.registry import DriverRegistry
|
||||
|
||||
from ..mock_client_driver import MockClientDriver
|
||||
|
||||
|
||||
class TestDriverRegistry(unittest.TestCase):
|
||||
"""Unit tests for the DriverRegistry class."""
|
||||
|
||||
def setUp(self):
|
||||
# Reset the singleton instance before each test
|
||||
DriverRegistry._singleton_instances = {}
|
||||
self.registry = DriverRegistry()
|
||||
self.registry.drivers = []
|
||||
|
||||
# Mock APRSISDriver completely
|
||||
self.aprsis_patcher = mock.patch('aprsd.client.drivers.aprsis.APRSISDriver')
|
||||
mock_aprsis_class = self.aprsis_patcher.start()
|
||||
mock_aprsis_class.is_enabled.return_value = False
|
||||
mock_aprsis_class.is_configured.return_value = False
|
||||
|
||||
# Mock the instance methods as well
|
||||
mock_instance = mock_aprsis_class.return_value
|
||||
mock_instance.is_enabled.return_value = False
|
||||
mock_instance.is_configured.return_value = False
|
||||
|
||||
# Mock CONF to prevent password check
|
||||
self.conf_patcher = mock.patch('aprsd.client.drivers.aprsis.CONF')
|
||||
mock_conf = self.conf_patcher.start()
|
||||
mock_conf.aprs_network.password = 'dummy'
|
||||
mock_conf.aprs_network.login = 'dummy'
|
||||
|
||||
def tearDown(self):
|
||||
# Reset the singleton instance after each test
|
||||
DriverRegistry().drivers = []
|
||||
self.aprsis_patcher.stop()
|
||||
self.conf_patcher.stop()
|
||||
|
||||
def test_get_driver_with_valid_driver(self):
|
||||
"""Test getting an enabled and configured driver."""
|
||||
# Add an enabled and configured driver
|
||||
driver = MockClientDriver
|
||||
driver.is_enabled = mock.MagicMock(return_value=True)
|
||||
driver.is_configured = mock.MagicMock(return_value=True)
|
||||
self.registry.register(MockClientDriver)
|
||||
|
||||
# Get the driver
|
||||
result = self.registry.get_driver()
|
||||
print(result)
|
||||
self.assertTrue(isinstance(result, MockClientDriver))
|
||||
|
||||
def test_get_driver_with_disabled_driver(self):
|
||||
"""Test getting a driver when only disabled drivers exist."""
|
||||
driver = MockClientDriver
|
||||
driver.is_enabled = mock.MagicMock(return_value=False)
|
||||
driver.is_configured = mock.MagicMock(return_value=False)
|
||||
self.registry.register(driver)
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.registry.get_driver()
|
||||
self.assertIn('No enabled driver found', str(context.exception))
|
||||
|
||||
def test_get_driver_with_unconfigured_driver(self):
|
||||
"""Test getting a driver when only unconfigured drivers exist."""
|
||||
driver = MockClientDriver
|
||||
driver.is_enabled = mock.MagicMock(return_value=True)
|
||||
driver.is_configured = mock.MagicMock(return_value=False)
|
||||
self.registry.register(driver)
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.registry.get_driver()
|
||||
self.assertIn('No enabled driver found', str(context.exception))
|
||||
|
||||
def test_get_driver_with_no_drivers(self):
|
||||
"""Test getting a driver when no drivers exist."""
|
||||
# Try to get a driver
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.registry.get_driver()
|
||||
self.assertIn('No enabled driver found', str(context.exception))
|
||||
|
||||
def test_get_driver_with_multiple_drivers(self):
|
||||
"""Test getting a driver when multiple valid drivers exist."""
|
||||
# Add multiple drivers
|
||||
driver1 = MockClientDriver
|
||||
driver1.is_enabled = mock.MagicMock(return_value=True)
|
||||
driver1.is_configured = mock.MagicMock(return_value=True)
|
||||
driver2 = MockClientDriver
|
||||
self.registry.register(driver1)
|
||||
self.registry.register(driver2)
|
||||
|
||||
# Get the driver - should return the first one
|
||||
result = self.registry.get_driver()
|
||||
# We can only check that it's a MockDriver instance
|
||||
self.assertTrue(isinstance(result, MockClientDriver))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,76 @@
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets import core
|
||||
|
||||
|
||||
class MockClientDriver:
|
||||
"""Mock implementation of ClientDriver for testing."""
|
||||
|
||||
def __init__(self, enabled=True, configured=True):
|
||||
self.connected = False
|
||||
self._alive = True
|
||||
self._keepalive = None
|
||||
self.filter = None
|
||||
self._enabled = enabled
|
||||
self._configured = configured
|
||||
self.path = '/dev/ttyUSB0'
|
||||
self.login_status = {
|
||||
'success': True,
|
||||
'message': None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
"""Static method to check if driver is enabled."""
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_configured():
|
||||
"""Static method to check if driver is configured."""
|
||||
return True
|
||||
|
||||
def is_alive(self):
|
||||
"""Instance method to check if driver is alive."""
|
||||
return self._alive
|
||||
|
||||
def stats(self, serializable=False):
|
||||
"""Return mock stats."""
|
||||
stats = {'packets_received': 0, 'packets_sent': 0}
|
||||
if serializable:
|
||||
stats['path'] = self.path
|
||||
return stats
|
||||
|
||||
@property
|
||||
def login_success(self):
|
||||
"""Property to get login success status."""
|
||||
return self.login_status['success']
|
||||
|
||||
@property
|
||||
def login_failure(self):
|
||||
"""Property to get login failure message."""
|
||||
return self.login_status['message']
|
||||
|
||||
def decode_packet(self, *args, **kwargs):
|
||||
"""Mock packet decoding."""
|
||||
packet = mock.MagicMock(spec=core.Packet)
|
||||
packet.raw = 'test packet'
|
||||
return packet
|
||||
|
||||
def close(self):
|
||||
self.connected = False
|
||||
|
||||
def setup_connection(self):
|
||||
self.connected = True
|
||||
|
||||
def send(self, packet):
|
||||
return True
|
||||
|
||||
def set_filter(self, filter_str):
|
||||
self.filter = filter_str
|
||||
|
||||
@property
|
||||
def keepalive(self):
|
||||
return self._keepalive
|
||||
|
||||
def consumer(self, callback, raw=False):
|
||||
pass
|
||||
@@ -7,9 +7,11 @@ from aprsd import ( # noqa: F401
|
||||
conf,
|
||||
packets,
|
||||
)
|
||||
from aprsd.client.drivers.registry import DriverRegistry
|
||||
from aprsd.plugins import notify as notify_plugin
|
||||
|
||||
from .. import fake, test_plugin
|
||||
from ..mock_client_driver import MockClientDriver
|
||||
|
||||
CONF = cfg.CONF
|
||||
DEFAULT_WATCHLIST_CALLSIGNS = fake.FAKE_FROM_CALLSIGN
|
||||
@@ -17,9 +19,24 @@ DEFAULT_WATCHLIST_CALLSIGNS = fake.FAKE_FROM_CALLSIGN
|
||||
|
||||
class TestWatchListPlugin(test_plugin.TestPlugin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
||||
self.ack = 1
|
||||
|
||||
# Mock APRSISDriver
|
||||
self.aprsis_patcher = mock.patch('aprsd.client.drivers.aprsis.APRSISDriver')
|
||||
self.mock_aprsis = self.aprsis_patcher.start()
|
||||
self.mock_aprsis.is_enabled.return_value = False
|
||||
self.mock_aprsis.is_configured.return_value = False
|
||||
|
||||
# Register the mock driver
|
||||
DriverRegistry().register(MockClientDriver)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
if hasattr(self, 'aprsis_patcher'):
|
||||
self.aprsis_patcher.stop()
|
||||
|
||||
def config_and_init(
|
||||
self,
|
||||
watchlist_enabled=True,
|
||||
@@ -30,7 +47,9 @@ class TestWatchListPlugin(test_plugin.TestPlugin):
|
||||
):
|
||||
CONF.callsign = self.fromcall
|
||||
CONF.aprs_network.login = self.fromcall
|
||||
CONF.aprs_fi.apiKey = "something"
|
||||
CONF.aprs_fi.apiKey = 'something'
|
||||
# Add mock password
|
||||
CONF.aprs_network.password = '12345'
|
||||
|
||||
# Set the watchlist specific config options
|
||||
CONF.watch_list.enabled = watchlist_enabled
|
||||
@@ -56,22 +75,20 @@ class TestAPRSDWatchListPluginBase(TestWatchListPlugin):
|
||||
plugin = fake.FakeWatchListPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
message="version",
|
||||
message='version',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
expected = packets.NULL_MESSAGE
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||
def test_watchlist_not_in_watchlist(self, mock_factory):
|
||||
client.client_factory = mock_factory
|
||||
def test_watchlist_not_in_watchlist(self):
|
||||
self.config_and_init()
|
||||
plugin = fake.FakeWatchListPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
fromcall="FAKE",
|
||||
message="version",
|
||||
fromcall='FAKE',
|
||||
message='version',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
@@ -85,87 +102,77 @@ class TestNotifySeenPlugin(TestWatchListPlugin):
|
||||
plugin = notify_plugin.NotifySeenPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
message="version",
|
||||
message='version',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
expected = packets.NULL_MESSAGE
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||
def test_callsign_not_in_watchlist(self, mock_factory):
|
||||
client.client_factory = mock_factory
|
||||
def test_callsign_not_in_watchlist(self):
|
||||
self.config_and_init(watchlist_enabled=False)
|
||||
plugin = notify_plugin.NotifySeenPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
message="version",
|
||||
message='version',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
expected = packets.NULL_MESSAGE
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||
def test_callsign_in_watchlist_not_old(self, mock_is_old, mock_factory):
|
||||
client.client_factory = mock_factory
|
||||
@mock.patch('aprsd.packets.WatchList.is_old')
|
||||
def test_callsign_in_watchlist_not_old(self, mock_is_old):
|
||||
mock_is_old.return_value = False
|
||||
self.config_and_init(
|
||||
watchlist_enabled=True,
|
||||
watchlist_callsigns=["WB4BOR"],
|
||||
watchlist_callsigns=['WB4BOR'],
|
||||
)
|
||||
plugin = notify_plugin.NotifySeenPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
fromcall="WB4BOR",
|
||||
message="ping",
|
||||
fromcall='WB4BOR',
|
||||
message='ping',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
expected = packets.NULL_MESSAGE
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||
def test_callsign_in_watchlist_old_same_alert_callsign(
|
||||
self, mock_is_old, mock_factory
|
||||
):
|
||||
client.client_factory = mock_factory
|
||||
@mock.patch('aprsd.packets.WatchList.is_old')
|
||||
def test_callsign_in_watchlist_old_same_alert_callsign(self, mock_is_old):
|
||||
mock_is_old.return_value = True
|
||||
self.config_and_init(
|
||||
watchlist_enabled=True,
|
||||
watchlist_alert_callsign="WB4BOR",
|
||||
watchlist_callsigns=["WB4BOR"],
|
||||
watchlist_alert_callsign='WB4BOR',
|
||||
watchlist_callsigns=['WB4BOR'],
|
||||
)
|
||||
plugin = notify_plugin.NotifySeenPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
fromcall="WB4BOR",
|
||||
message="ping",
|
||||
fromcall='WB4BOR',
|
||||
message='ping',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = plugin.filter(packet)
|
||||
expected = packets.NULL_MESSAGE
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@mock.patch("aprsd.client.factory.ClientFactory", autospec=True)
|
||||
@mock.patch("aprsd.packets.WatchList.is_old")
|
||||
def test_callsign_in_watchlist_old_send_alert(self, mock_is_old, mock_factory):
|
||||
client.client_factory = mock_factory
|
||||
@mock.patch('aprsd.packets.WatchList.is_old')
|
||||
def test_callsign_in_watchlist_old_send_alert(self, mock_is_old):
|
||||
mock_is_old.return_value = True
|
||||
notify_callsign = fake.FAKE_TO_CALLSIGN
|
||||
fromcall = "WB4BOR"
|
||||
fromcall = 'WB4BOR'
|
||||
self.config_and_init(
|
||||
watchlist_enabled=True,
|
||||
watchlist_alert_callsign=notify_callsign,
|
||||
watchlist_callsigns=["WB4BOR"],
|
||||
watchlist_callsigns=['WB4BOR'],
|
||||
)
|
||||
plugin = notify_plugin.NotifySeenPlugin()
|
||||
|
||||
packet = fake.fake_packet(
|
||||
fromcall=fromcall,
|
||||
message="ping",
|
||||
message='ping',
|
||||
msg_number=1,
|
||||
)
|
||||
packet_type = packet.__class__.__name__
|
||||
|
||||
@@ -3,6 +3,7 @@ from unittest import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
import aprsd
|
||||
from aprsd.client.drivers.fake import APRSDFakeDriver
|
||||
from aprsd.plugins import version as version_plugin
|
||||
|
||||
from .. import fake, test_plugin
|
||||
@@ -11,16 +12,41 @@ CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestVersionPlugin(test_plugin.TestPlugin):
|
||||
@mock.patch("aprsd.stats.app.APRSDStats.uptime")
|
||||
def test_version(self, mock_stats):
|
||||
mock_stats.return_value = "00:00:00"
|
||||
expected = f"APRSD ver:{aprsd.__version__} uptime:00:00:00"
|
||||
def setUp(self):
|
||||
# make sure the fake client driver is enabled
|
||||
# Mock CONF for testing
|
||||
super().setUp()
|
||||
self.conf_patcher = mock.patch('aprsd.client.drivers.fake.CONF')
|
||||
self.mock_conf = self.conf_patcher.start()
|
||||
|
||||
# Configure fake_client.enabled
|
||||
self.mock_conf.fake_client.enabled = True
|
||||
|
||||
# Create an instance of the driver
|
||||
self.driver = APRSDFakeDriver()
|
||||
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
||||
|
||||
def tearDown(self):
|
||||
self.conf_patcher.stop()
|
||||
super().tearDown()
|
||||
|
||||
@mock.patch('aprsd.stats.collector.Collector')
|
||||
def test_version(self, mock_collector_class):
|
||||
# Set up the mock collector instance
|
||||
mock_collector_instance = mock_collector_class.return_value
|
||||
mock_collector_instance.collect.return_value = {
|
||||
'APRSDStats': {
|
||||
'uptime': '00:00:00',
|
||||
}
|
||||
}
|
||||
|
||||
expected = f'APRSD ver:{aprsd.__version__} uptime:00:00:00'
|
||||
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||
version = version_plugin.VersionPlugin()
|
||||
version.enabled = True
|
||||
|
||||
packet = fake.fake_packet(
|
||||
message="No",
|
||||
message='No',
|
||||
msg_number=1,
|
||||
)
|
||||
|
||||
@@ -28,8 +54,11 @@ class TestVersionPlugin(test_plugin.TestPlugin):
|
||||
self.assertEqual(None, actual)
|
||||
|
||||
packet = fake.fake_packet(
|
||||
message="version",
|
||||
message='version',
|
||||
msg_number=1,
|
||||
)
|
||||
actual = version.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
# Verify the mock was called exactly once
|
||||
mock_collector_instance.collect.assert_called_once()
|
||||
|
||||
+38
-18
@@ -9,9 +9,11 @@ from aprsd import ( # noqa: F401
|
||||
plugins,
|
||||
)
|
||||
from aprsd import plugin as aprsd_plugin
|
||||
from aprsd.client.drivers.registry import DriverRegistry
|
||||
from aprsd.packets import core
|
||||
|
||||
from . import fake
|
||||
from .mock_client_driver import MockClientDriver
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -21,15 +23,24 @@ class TestPluginManager(unittest.TestCase):
|
||||
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
||||
self.config_and_init()
|
||||
|
||||
self.mock_driver = MockClientDriver()
|
||||
# Mock the DriverRegistry to return our mock driver
|
||||
self.registry_patcher = mock.patch.object(
|
||||
DriverRegistry, 'get_driver', return_value=self.mock_driver
|
||||
)
|
||||
self.mock_registry = self.registry_patcher.start()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.config = None
|
||||
aprsd_plugin.PluginManager._instance = None
|
||||
self.registry_patcher.stop()
|
||||
self.mock_registry.stop()
|
||||
|
||||
def config_and_init(self):
|
||||
CONF.callsign = self.fromcall
|
||||
CONF.aprs_network.login = fake.FAKE_TO_CALLSIGN
|
||||
CONF.aprs_fi.apiKey = "something"
|
||||
CONF.enabled_plugins = "aprsd.plugins.ping.PingPlugin"
|
||||
CONF.aprs_fi.apiKey = 'something'
|
||||
CONF.enabled_plugins = 'aprsd.plugins.ping.PingPlugin'
|
||||
CONF.enable_save = False
|
||||
|
||||
def test_get_plugins_no_plugins(self):
|
||||
@@ -39,7 +50,7 @@ class TestPluginManager(unittest.TestCase):
|
||||
self.assertEqual([], plugin_list)
|
||||
|
||||
def test_get_plugins_with_plugins(self):
|
||||
CONF.enabled_plugins = ["aprsd.plugins.ping.PingPlugin"]
|
||||
CONF.enabled_plugins = ['aprsd.plugins.ping.PingPlugin']
|
||||
pm = aprsd_plugin.PluginManager()
|
||||
plugin_list = pm.get_plugins()
|
||||
self.assertEqual([], plugin_list)
|
||||
@@ -64,7 +75,7 @@ class TestPluginManager(unittest.TestCase):
|
||||
self.assertEqual(0, len(plugin_list))
|
||||
|
||||
def test_get_message_plugins(self):
|
||||
CONF.enabled_plugins = ["aprsd.plugins.ping.PingPlugin"]
|
||||
CONF.enabled_plugins = ['aprsd.plugins.ping.PingPlugin']
|
||||
pm = aprsd_plugin.PluginManager()
|
||||
plugin_list = pm.get_plugins()
|
||||
self.assertEqual([], plugin_list)
|
||||
@@ -87,22 +98,31 @@ class TestPlugin(unittest.TestCase):
|
||||
self.ack = 1
|
||||
self.config_and_init()
|
||||
|
||||
self.mock_driver = MockClientDriver()
|
||||
# Mock the DriverRegistry to return our mock driver
|
||||
self.registry_patcher = mock.patch.object(
|
||||
DriverRegistry, 'get_driver', return_value=self.mock_driver
|
||||
)
|
||||
self.mock_registry = self.registry_patcher.start()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
packets.WatchList._instance = None
|
||||
packets.SeenList._instance = None
|
||||
packets.PacketTrack._instance = None
|
||||
self.config = None
|
||||
self.registry_patcher.stop()
|
||||
self.mock_registry.stop()
|
||||
|
||||
def config_and_init(self):
|
||||
CONF.callsign = self.fromcall
|
||||
CONF.aprs_network.login = fake.FAKE_TO_CALLSIGN
|
||||
CONF.aprs_fi.apiKey = "something"
|
||||
CONF.enabled_plugins = "aprsd.plugins.ping.PingPlugin"
|
||||
CONF.aprs_fi.apiKey = 'something'
|
||||
CONF.enabled_plugins = 'aprsd.plugins.ping.PingPlugin'
|
||||
CONF.enable_save = False
|
||||
|
||||
|
||||
class TestPluginBase(TestPlugin):
|
||||
@mock.patch.object(fake.FakeBaseNoThreadsPlugin, "process")
|
||||
@mock.patch.object(fake.FakeBaseNoThreadsPlugin, 'process')
|
||||
def test_base_plugin_no_threads(self, mock_process):
|
||||
p = fake.FakeBaseNoThreadsPlugin()
|
||||
|
||||
@@ -110,7 +130,7 @@ class TestPluginBase(TestPlugin):
|
||||
actual = p.create_threads()
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
expected = "1.0"
|
||||
expected = '1.0'
|
||||
actual = p.version
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
@@ -123,7 +143,7 @@ class TestPluginBase(TestPlugin):
|
||||
self.assertEqual(expected, actual)
|
||||
mock_process.assert_not_called()
|
||||
|
||||
@mock.patch.object(fake.FakeBaseThreadsPlugin, "create_threads")
|
||||
@mock.patch.object(fake.FakeBaseThreadsPlugin, 'create_threads')
|
||||
def test_base_plugin_threads_created(self, mock_create):
|
||||
p = fake.FakeBaseThreadsPlugin()
|
||||
mock_create.assert_called_once()
|
||||
@@ -135,17 +155,17 @@ class TestPluginBase(TestPlugin):
|
||||
self.assertTrue(isinstance(actual, fake.FakeThread))
|
||||
p.stop_threads()
|
||||
|
||||
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
||||
@mock.patch.object(fake.FakeRegexCommandPlugin, 'process')
|
||||
def test_regex_base_not_called(self, mock_process):
|
||||
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||
p = fake.FakeRegexCommandPlugin()
|
||||
packet = fake.fake_packet(message="a")
|
||||
packet = fake.fake_packet(message='a')
|
||||
expected = None
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
mock_process.assert_not_called()
|
||||
|
||||
packet = fake.fake_packet(tocall="notMe", message="f")
|
||||
packet = fake.fake_packet(tocall='notMe', message='f')
|
||||
expected = None
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
@@ -165,11 +185,11 @@ class TestPluginBase(TestPlugin):
|
||||
self.assertEqual(expected, actual)
|
||||
mock_process.assert_not_called()
|
||||
|
||||
@mock.patch.object(fake.FakeRegexCommandPlugin, "process")
|
||||
@mock.patch.object(fake.FakeRegexCommandPlugin, 'process')
|
||||
def test_regex_base_assert_called(self, mock_process):
|
||||
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||
p = fake.FakeRegexCommandPlugin()
|
||||
packet = fake.fake_packet(message="f")
|
||||
packet = fake.fake_packet(message='f')
|
||||
p.filter(packet)
|
||||
mock_process.assert_called_once()
|
||||
|
||||
@@ -177,22 +197,22 @@ class TestPluginBase(TestPlugin):
|
||||
CONF.callsign = fake.FAKE_TO_CALLSIGN
|
||||
p = fake.FakeRegexCommandPlugin()
|
||||
|
||||
packet = fake.fake_packet(message="f")
|
||||
packet = fake.fake_packet(message='f')
|
||||
expected = fake.FAKE_MESSAGE_TEXT
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
packet = fake.fake_packet(message="F")
|
||||
packet = fake.fake_packet(message='F')
|
||||
expected = fake.FAKE_MESSAGE_TEXT
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
packet = fake.fake_packet(message="fake")
|
||||
packet = fake.fake_packet(message='fake')
|
||||
expected = fake.FAKE_MESSAGE_TEXT
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
packet = fake.fake_packet(message="FAKE")
|
||||
packet = fake.fake_packet(message='FAKE')
|
||||
expected = fake.FAKE_MESSAGE_TEXT
|
||||
actual = p.filter(packet)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
Reference in New Issue
Block a user