1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-01-13 09:07:35 -05:00
aprsd/tests/threads/test_rx.py
2025-12-09 17:20:23 -05:00

376 lines
14 KiB
Python

import queue
import unittest
from unittest import mock
from aprsd.threads import rx
from tests import fake
from tests.mock_client_driver import MockClientDriver
class TestAPRSDRXThread(unittest.TestCase):
"""Unit tests for the APRSDRXThread class."""
def setUp(self):
"""Set up test fixtures."""
self.packet_queue = queue.Queue()
self.rx_thread = rx.APRSDRXThread(self.packet_queue)
self.rx_thread.pkt_count = 0 # Reset packet count
def tearDown(self):
"""Clean up after tests."""
self.rx_thread.stop()
if self.rx_thread.is_alive():
self.rx_thread.join(timeout=1)
def test_init(self):
"""Test initialization."""
self.assertEqual(self.rx_thread.name, 'RX_PKT')
self.assertEqual(self.rx_thread.packet_queue, self.packet_queue)
self.assertEqual(self.rx_thread.pkt_count, 0)
self.assertIsNone(self.rx_thread._client)
def test_stop(self):
"""Test stop() method."""
self.rx_thread._client = mock.MagicMock()
self.rx_thread.stop()
self.assertTrue(self.rx_thread.thread_stop)
self.rx_thread._client.close.assert_called()
def test_stop_no_client(self):
"""Test stop() when client is None."""
self.rx_thread.stop()
self.assertTrue(self.rx_thread.thread_stop)
def test_loop_no_client(self):
"""Test loop() when client is None."""
with mock.patch('aprsd.threads.rx.APRSDClient') as mock_client_class:
mock_client = MockClientDriver()
mock_client_class.return_value = mock_client
result = self.rx_thread.loop()
self.assertTrue(result)
self.assertIsNotNone(self.rx_thread._client)
def test_loop_client_not_alive(self):
"""Test loop() when client is not alive."""
from aprsd.client.client import APRSDClient
# Reset singleton
APRSDClient._instance = None
mock_client = MockClientDriver()
mock_client._alive = False
self.rx_thread._client = mock_client
with mock.patch('aprsd.threads.rx.APRSDClient') as mock_client_class:
new_client_instance = mock.MagicMock()
new_client_instance.driver = MockClientDriver()
new_client_instance.is_alive = True
mock_client_class.return_value = new_client_instance
result = self.rx_thread.loop()
self.assertTrue(result)
# Client should be replaced
self.assertIsNotNone(self.rx_thread._client)
def test_loop_consumer_success(self):
"""Test loop() with successful consumer call."""
mock_client = MockClientDriver()
mock_client._alive = True
callback_called = []
mock_client._consumer_callback = lambda cb: callback_called.append(True)
self.rx_thread._client = mock_client
result = self.rx_thread.loop()
self.assertTrue(result)
self.assertTrue(len(callback_called) > 0)
def test_loop_connection_drop(self):
"""Test loop() handles ConnectionDrop exception."""
import aprslib
mock_client = MockClientDriver()
mock_client._alive = True
mock_client._consumer_side_effect = aprslib.exceptions.ConnectionDrop(
'Connection dropped'
)
self.rx_thread._client = mock_client
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
with mock.patch.object(mock_client, 'reset') as mock_reset:
result = self.rx_thread.loop()
self.assertTrue(result)
mock_log.error.assert_called()
mock_reset.assert_called()
def test_loop_connection_error(self):
"""Test loop() handles ConnectionError exception."""
import aprslib
mock_client = MockClientDriver()
mock_client._alive = True
mock_client._consumer_side_effect = aprslib.exceptions.ConnectionError(
'Connection error'
)
self.rx_thread._client = mock_client
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
with mock.patch.object(mock_client, 'reset') as mock_reset:
result = self.rx_thread.loop()
self.assertTrue(result)
mock_log.error.assert_called()
mock_reset.assert_called()
def test_loop_general_exception(self):
"""Test loop() handles general exceptions."""
mock_client = MockClientDriver()
mock_client._alive = True
mock_client._consumer_side_effect = Exception('General error')
self.rx_thread._client = mock_client
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
with mock.patch.object(mock_client, 'reset') as mock_reset:
result = self.rx_thread.loop()
self.assertTrue(result)
mock_log.exception.assert_called()
mock_log.error.assert_called()
mock_reset.assert_called()
def test_process_packet(self):
"""Test process_packet() method."""
mock_client = MockClientDriver()
packet = fake.fake_packet(msg_number='123')
mock_client._decode_packet_return = packet
self.rx_thread._client = mock_client
self.rx_thread.pkt_count = 0
with mock.patch('aprsd.threads.rx.packet_log'):
with mock.patch('aprsd.threads.rx.packets.PacketList') as mock_pkt_list:
mock_list_instance = mock.MagicMock()
mock_list_instance.find.side_effect = KeyError('Not found')
mock_pkt_list.return_value = mock_list_instance
self.rx_thread.process_packet()
self.assertEqual(self.rx_thread.pkt_count, 1)
self.assertFalse(self.packet_queue.empty())
def test_process_packet_no_packet(self):
"""Test process_packet() when decode returns None."""
mock_client = MockClientDriver()
mock_client._decode_packet_return = None
self.rx_thread._client = mock_client
self.rx_thread.pkt_count = 0
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
self.rx_thread.process_packet()
mock_log.error.assert_called()
self.assertEqual(self.rx_thread.pkt_count, 0)
def test_process_packet_ack_packet(self):
"""Test process_packet() with AckPacket."""
mock_client = MockClientDriver()
packet = fake.fake_ack_packet()
mock_client._decode_packet_return = packet
self.rx_thread._client = mock_client
self.rx_thread.pkt_count = 0
with mock.patch('aprsd.threads.rx.packet_log'):
self.rx_thread.process_packet()
self.assertEqual(self.rx_thread.pkt_count, 1)
self.assertFalse(self.packet_queue.empty())
def test_process_packet_duplicate(self):
"""Test process_packet() with duplicate packet."""
from oslo_config import cfg
CONF = cfg.CONF
CONF.packet_dupe_timeout = 60
mock_client = MockClientDriver()
packet = fake.fake_packet(msg_number='123')
packet.timestamp = 1000
mock_client._decode_packet_return = packet
self.rx_thread._client = mock_client
self.rx_thread.pkt_count = 0
with mock.patch('aprsd.threads.rx.packet_log'):
with mock.patch('aprsd.threads.rx.packets.PacketList') as mock_pkt_list:
mock_list_instance = mock.MagicMock()
found_packet = fake.fake_packet(msg_number='123')
found_packet.timestamp = 1050 # Within timeout
mock_list_instance.find.return_value = found_packet
mock_pkt_list.return_value = mock_list_instance
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
self.rx_thread.process_packet()
mock_log.warning.assert_called()
# Should not add to queue
self.assertTrue(self.packet_queue.empty())
class TestAPRSDFilterThread(unittest.TestCase):
"""Unit tests for the APRSDFilterThread class."""
def setUp(self):
"""Set up test fixtures."""
self.packet_queue = queue.Queue()
class TestFilterThread(rx.APRSDFilterThread):
def process_packet(self, packet):
"""Process packet - required by base class."""
pass
self.filter_thread = TestFilterThread('TestFilterThread', self.packet_queue)
def tearDown(self):
"""Clean up after tests."""
self.filter_thread.stop()
if self.filter_thread.is_alive():
self.filter_thread.join(timeout=1)
def test_init(self):
"""Test initialization."""
self.assertEqual(self.filter_thread.name, 'TestFilterThread')
self.assertEqual(self.filter_thread.packet_queue, self.packet_queue)
def test_filter_packet(self):
"""Test filter_packet() method."""
packet = fake.fake_packet()
with mock.patch('aprsd.threads.rx.filter.PacketFilter') as mock_filter:
mock_filter_instance = mock.MagicMock()
mock_filter_instance.filter.return_value = packet
mock_filter.return_value = mock_filter_instance
result = self.filter_thread.filter_packet(packet)
self.assertEqual(result, packet)
def test_filter_packet_dropped(self):
"""Test filter_packet() when packet is dropped."""
packet = fake.fake_packet()
with mock.patch('aprsd.threads.rx.filter.PacketFilter') as mock_filter:
mock_filter_instance = mock.MagicMock()
mock_filter_instance.filter.return_value = None
mock_filter.return_value = mock_filter_instance
result = self.filter_thread.filter_packet(packet)
self.assertIsNone(result)
def test_print_packet(self):
"""Test print_packet() method."""
packet = fake.fake_packet()
with mock.patch('aprsd.threads.rx.packet_log') as mock_log:
self.filter_thread.print_packet(packet)
mock_log.log.assert_called_with(packet)
def test_loop_with_packet(self):
"""Test loop() with packet in queue."""
packet = fake.fake_packet()
self.packet_queue.put(packet)
with mock.patch.object(
self.filter_thread, 'filter_packet', return_value=packet
):
with mock.patch.object(self.filter_thread, 'print_packet'):
result = self.filter_thread.loop()
self.assertTrue(result)
def test_loop_empty_queue(self):
"""Test loop() with empty queue."""
result = self.filter_thread.loop()
self.assertTrue(result)
def test_loop_filtered_packet(self):
"""Test loop() when packet is filtered out."""
packet = fake.fake_packet()
self.packet_queue.put(packet)
with mock.patch.object(self.filter_thread, 'filter_packet', return_value=None):
with mock.patch.object(self.filter_thread, 'print_packet'):
result = self.filter_thread.loop()
self.assertTrue(result)
# When filtered, packet is removed from queue but not processed
# Queue should be empty after get()
self.assertTrue(self.packet_queue.empty())
class TestAPRSDProcessPacketThread(unittest.TestCase):
"""Unit tests for the APRSDProcessPacketThread class."""
def setUp(self):
"""Set up test fixtures."""
self.packet_queue = queue.Queue()
class ConcreteProcessThread(rx.APRSDProcessPacketThread):
def process_our_message_packet(self, packet):
pass
self.process_thread = ConcreteProcessThread(self.packet_queue)
def tearDown(self):
"""Clean up after tests."""
self.process_thread.stop()
if self.process_thread.is_alive():
self.process_thread.join(timeout=1)
def test_init(self):
"""Test initialization."""
self.assertEqual(self.process_thread.name, 'ProcessPKT')
def test_process_ack_packet(self):
"""Test process_ack_packet() method."""
from oslo_config import cfg
from aprsd.packets import collector
CONF = cfg.CONF
CONF.callsign = 'TEST'
packet = fake.fake_ack_packet()
packet.addresse = 'TEST'
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
self.process_thread.process_ack_packet(packet)
mock_rx.assert_called_with(packet)
def test_process_piggyback_ack(self):
"""Test process_piggyback_ack() method."""
from aprsd.packets import collector
packet = fake.fake_packet()
packet.ackMsgNo = '123'
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
self.process_thread.process_piggyback_ack(packet)
mock_rx.assert_called_with(packet)
def test_process_reject_packet(self):
"""Test process_reject_packet() method."""
from aprsd.packets import collector
packet = fake.fake_packet()
packet.msgNo = '123'
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
self.process_thread.process_reject_packet(packet)
mock_rx.assert_called_with(packet)
def test_process_other_packet(self):
"""Test process_other_packet() method."""
packet = fake.fake_packet()
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
self.process_thread.process_other_packet(packet, for_us=False)
mock_log.info.assert_called()
self.process_thread.process_other_packet(packet, for_us=True)
self.assertEqual(mock_log.info.call_count, 2)